package com.chunlin.string;

import java.io.UnsupportedEncodingException;
import java.lang.reflect.Field;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;

public class StringTest {

    //String类提供的构造方式非常多，常用的就以下三种：
    public static void test1() {
        // 使用常量串构造
        String s1 = "hello world";
        System.out.println(s1);
        // 直接newString对象
        String s2 = new String("hello world");
        System.out.println(s1);
        // 使用字符数组进行构造
        char[] array = {'h', 'e', 'l', 'l', 'o', 'w', 'o', 'r', 'l', 'd'};
        String s3 = new String(array);
        System.out.println(s1);
    }

    public static void test2() {
        String str1 = "hello";
        String str2 = "hello";
        String str3 = new String("hello");

        //str1 和 str2 都引用了字符串池中的同一个 "hello" 对象
        //str3 创建了一个新的字符串对象，即使内容相同，但它在内存中是一个不同的对象

        System.out.println(str1 == str2); // true，因为它们引用同一个字符串对象
        System.out.println(str1 == str3); // false，因为它们引用不同的字符串对象
        System.out.println(str1.equals(str3)); // true，因为它们的内容相同

        //在Java中""引起来的也是String类型对象
        System.out.println("hello".length());
        System.out.println("hello".equals(str3));
        System.out.println("hello" == str1);
    }

    //String对象的比较
    //字符串的比较是常见操作之一，比如：字符串排序。Java中总共提供了4中方式：
    public static void test3() {
        //1. ==比较是否引用同一个对象
        //注意：对于内置类型，==比较的是变量中的值；对于引用类型==比较的是引用中的地址

        System.out.println("==");
        int a = 10;
        int b = 20;
        int c = 10;
        // 对于基本类型变量，==比较两个变量中存储的值是否相同
        System.out.println(a == b); // false
        System.out.println(a == c); // true
        // 对于引用类型变量，==比较两个引用变量引用的是否为同一个对象
        String s1 = new String("hello");
        String s2 = new String("hello");
        String s3 = new String("world");
        String s4 = s1;
        System.out.println(s1 == s2); // false
        System.out.println(s2 == s3); // false
        System.out.println(s1 == s4); // true
        System.out.println("================================================");
        //boolean equals(Object anObject) 方法：按照字典序比较
        //字典序：字符大小的顺序
        //String类重写了父类Object中equals方法，Object中equals默认按照==比较，String重写equals方法后,按照字典序来进行大小比较
        System.out.println("equals");
        String s5 = new String("hello");
        String s6 = new String("hello");
        String s7 = new String("Hello");
        // s1、s2、s3引用的是三个不同对象，因此==比较结果全部为false
        System.out.println(s5 == s6); // false
        System.out.println(s5 == s7); // false
        // equals比较：String对象中的逐个字符
        // 虽然s1与s2引用的不是同一个对象，但是两个对象中放置的内容相同，因此输出true
        // s1与s3引用的不是同一个对象，而且两个对象中内容也不同，因此输出false
        System.out.println(s5.equals(s6)); // true
        System.out.println(s5.equals(s7)); // false
        System.out.println("================================================");
        //与equals不同的是，equals返回的是boolean类型，而compareTo返回的是int类型。具体比较方式：
        //1. 先按照字典次序大小比较，如果出现不等的字符，直接返回这两个字符的大小差值
        //2. 如果前k个字符相等(k为两个字符长度最小值)，返回值两个字符串长度差值
        System.out.println("compareTo");
        String s8 = new String("abc");
        String s9 = new String("ac");
        String s10 = new String("abc");
        String s11 = new String("abcdef");
        String s12 = new String("ab");
        String s13 = new String("Ab");
        System.out.println(s8.compareTo(s9));  // 不同输出字符差值-1
        System.out.println(s8.compareTo(s10)); // 相同输出 0
        System.out.println(s8.compareTo(s11)); // 前k个字符完全相同，输出长度差值-3
        System.out.println(s8.compareTo(s12)); // 不同输出字符差值1
        System.out.println(s8.compareTo(s13)); // 不同输出字符差值32,大小写差距32的ASCII值
        System.out.println("================================================");
        //与compareTo方式相同，但是忽略大小写比较
        System.out.println("compareToIgnoreCase");
        String s14 = new String("abc");
        String s15 = new String("ac");
        String s16 = new String("ABc");
        String s17 = new String("abcdef");
        System.out.println(s14.compareToIgnoreCase(s15)); // 不同输出字符差值-1
        System.out.println(s14.compareToIgnoreCase(s16)); // 相同输出 0
        System.out.println(s14.compareToIgnoreCase(s17)); // 前k个字符完全相同，输出长度差值-3
    }

    //字符串查找
    public static void test4() {
        String str = "aaabbbcccaaabbbccc";

        //char charAt(int index)
        //返回index位置上字符，如果index为负数或者越界，抛出IndexOutOfBoundsException异常
        System.out.println(str.charAt(3));

        //int indexOf(int ch)
        //返回ch第一次出现的位置，没有返回-1
        System.out.println(str.indexOf('b'));  //第一次出现的位置是下标3
        System.out.println(str.indexOf('d'));  //这个元素并没有出现,所以返回的下标是-1

        //int indexOf(int ch, int fromIndex)
        //从fromIndex位置开始找ch第一次出现的位置，没有返回-1
        System.out.println(str.indexOf('a', 3));  //从下标为3的位置开始找'a'的第一次出现位置,返回下标为9

        //int indexOf(String str)
        //返回str第一次出现的位置，没有返回-1
        System.out.println(str.indexOf("aabb"));  //第一次出现的位置是下标1

        //int indexOf(String str, int fromIndex)
        //从fromIndex位置开始找str第一次出现的位置，没有返回-1
        System.out.println(str.indexOf("aabb", 5)); //从下标为5的位置开始找"aabb"的第一次出现位置,返回下标为10

        //int lastIndexOf(int ch)
        //从后往前找，返回ch第一次出现的位置，没有返回-1
        System.out.println(str.lastIndexOf('a'));

        //int lastIndexOf(int ch, int fromIndex)
        //从fromIndex位置开始找，从后往前找ch第一次出现的位置，没有返回-1
        System.out.println(str.lastIndexOf('a', 8));

        //int lastIndexOf(String str)
        //从后往前找，返回str第一次出现的位置，没有返回-1
        System.out.println(str.lastIndexOf("aabb"));

        //int lastIndexOf(String str, int fromIndex)
        //从fromIndex位置开始找，从后往前找str第一次出现的位置，没有返回-1
        System.out.println(str.lastIndexOf("aabb", 9));
    }

    //字符串转化
    public static void test5() {
        //数字和字符串转换
        String str1 = String.valueOf(1234);
        String str2 = String.valueOf(12.34);
        String str3 = String.valueOf(true);
        //如果是自定义对象就要自己去重写toString方法
        String str4 = String.valueOf(new Student("Hanmeimei", 18));

        System.out.println(str1);
        System.out.println(str2);
        System.out.println(str3);
        System.out.println(str4);
        System.out.println("=================================");
        // 字符串转数字
        // 注意：Integer、Double等是Java中的包装类型
        int data1 = Integer.parseInt("1234");
        double data2 = Double.parseDouble("12.34");
        System.out.println(data1);
        System.out.println(data2);
        System.out.println("=================================");
        //大小写转换
        String str5 = "hello";
        String str6 = "HELLO";
        //小写转成大写
        System.out.println(str5.toUpperCase());
        //大写转成小写
        System.out.println(str6.toLowerCase());
        System.out.println("=================================");
        //字符串转数组
        String str7 = "world";
        char[] ch = str7.toCharArray();
        for (int i = 0; i < ch.length; ++i) {
            System.out.println(ch[i]);
        }
        //数组转字符串
        String str8 = new String(ch);
        System.out.println(str8);
        System.out.println("=================================");
        //字符串格式化
        String str9 = String.format("%d-%d-%d", 2024, 7, 18);
        System.out.println(str9);
    }

    //字符串替换
    //使用一个指定的新的字符串替换掉已有的字符串数据
    public static void test6() {
        String str1 = "Hello World!";
        //replace 方法用于替换字符串中的字符或字符串序列,不能使用正则表达式
        System.out.println(str1.replace('l', 'x'));
        System.out.println(str1.replace("ll", "xx"));

        //replaceAll 方法用于替换所有匹配给定的正则表达式的子字符串
        String str2 = "hello123world456";
        System.out.println(str2.replaceAll("\\d", "pi"));

        //replaceFirst 方法用于替换第一个匹配给定正则表达式的子字符串
        String str3 = "hello123world456";
        System.out.println(str3.replaceFirst("\\d", "pi"));

        //注意事项: 由于字符串是不可变对象, 替换不修改当前字符串, 而是产生一个新的字符串,需要进行接收
    }

    //字符串拆分
    public static void test7() {
        String str1 = "one,two,three,four,,,,";
        //String[] split(String regex)
        //使用正则表达式regex分割字符串，并返回分割后的子字符串数组。默认情况下，所有匹配正则表达式的地方都会进行分割
        String[] result = str1.split(",");
        for (String str : result) {
            System.out.println(str);
        }

        //String[] split(String regex, int limit)
        //使用正则表达式regex分割字符串，最多返回limit个子字符串。limit参数的含义如下：
        //零：与split(String regex)效果相同，删除尾部空字符串
        //负值：分割无限制，并包括所有尾部空字符串
        String[] result1 = str1.split(",", 2);
        for (String str : result1) {
            System.out.println(str);
        }

        String[] result2 = str1.split(",", 0);
        for (String str : result2) {
            System.out.println(str);
        }

        String[] result3 = str1.split(",", -1);
        for (String str : result3) {
            System.out.println(str);
        }

        //有些特殊字符作为分割符可能无法正确切分, 需要加上转义
        //因为点 (.) 在正则表达式中是一个特殊字符，它匹配任何字符。因此，直接使用 "." 作为分隔符，会导致错误的结果，因为它会匹配字符串中的每个字符
        String IP = "192.168.1.1";
        //String[] ret = IP.split(".");
        //为了正确地使用点号作为分隔符，需要对点号进行转义。转义字符在正则表达式中是反斜杠（\）
        //但由于反斜杠在字符串中本身也需要转义，所以在Java字符串中需要使用双反斜杠（\\）
        String[] ret = IP.split("\\.");
        for (String str : ret) {
            System.out.println(str);
        }

        //注意事项:
        //1. 字符"|","*","+"都得加上转义字符，前面加上"\\"
        //2. 而如果是"\" ，那么就得写成"\\\\"
        //在Java中，反斜杠 (\) 是一个转义字符。为了在字符串中表示一个反斜杠，需要用两个反斜杠 (\\)
        String str2 = "path\\to\\file";
        System.out.println(str2);
        //在正则表达式中，一个反斜杠需要转义为两个反斜杠 (\\)
        //在Java字符串中，每个反斜杠需要再转义一次，所以最终需要四个反斜杠 (\\\\) 来表示一个反斜杠字符
        String[] ret1 = str2.split("\\\\");
        for (String str : ret1) {
            System.out.println(str);
        }
        //3. 如果一个字符串中有多个分隔符，可以用"|"作为连字符
        //也可以使用"[]",来匹配当中的任意一个字符
        String str3 = "apple,orange;banana";
        String[] ret3 = str3.split(",|;");
        for (String str : ret3) {
            System.out.println(str);
        }

        String[] ret4 = str3.split("[,;]");
        for (String str : ret4) {
            System.out.println(str);
        }

        //多次拆分
        String info = "name=xizolei&age=23";
        String[] ret5 = info.split("&");
        for (String str : ret5) {
            String[] temp = str.split("=");
            //String.split 方法在任何情况下都会返回一个非空数组。即使字符串为空（例如，"".split("=")），返回的数组也至少包含一个空字符串元素
            for (String s : temp) {
                System.out.println(s);
            }
        }
    }

    //字符串截取
    //从一个完整的字符串之中截取出部分内容
    public static void test8() {
        String str1 = "hello world";
        //String substring(int beginIndex)
        //从指定索引截取到结尾
        System.out.println(str1.substring(2));
        //截取部分内容
        //String substring(int beginIndex, int endIndex)
        //这个区间是前闭后开,也就是说截取2到4的字符串
        System.out.println(str1.substring(2, 5));
    }

    //其他操作方法
    public static void test9() {
        String str = " hello world  ";
        System.out.println("[" + str + "]");
        //String trim()
        //去掉字符串开头和结尾的空白字符(空格, 换行, 制表符等)
        System.out.println("[" + str.trim() + "]");
    }

    public static void test10() {
        //String设计上是一种不可变对象,字符串中的内容是不可改变。字符串不可被修改
        //String类中的字符实际保存在内部维护的value字符数组中
        //String类被final修饰，表明该类不能被继承
        //value被修饰被final修饰，表明value自身的值不能改变，即不能引用其它字符数组，但是其引用空间中的内容可以修改
        //下面的例子可以说明
        char[] array = new char[]{'1', '2', '3'};
        Test test = new Test(array);
        System.out.println(test);
        //value不能改变引用
//        char[] array1 = new char[]{'1','2','3','4'};
//        test.value = array1;
        //但是value里面的值可以改变
        //String中的value因为是private的,所以在其他类中也不能进行改变
        test.value[0] = '4';
        System.out.println(test);

        //为什么 String 要设计成不可变的?(不可变对象的好处是什么?)
        //1.线程安全
        //不可变对象是天生的线程安全的。因为不可变对象的状态在创建后不能改变，所以多线程环境中多个线程可以安全地共享同一个不可变对象而不需要同步
        //2.性能优化
        //不可变对象可以被安全地缓存和重用。Java的字符串池（String Pool）机制就是基于字符串的不可变性
        // 在Java中，字符串常量池是一种优化机制，它允许复用相同的字符串对象，减少内存使用和提高性能
        //3.哈希码缓存
        //不可变对象可以缓存哈希码，因为它们的状态不会改变，哈希码也不会改变。因此，每次调用字符串的 hashCode() 方法时，不需要重新计算哈希码，提高了效率
    }

    //字符串修改
    public static void test11() {
        //注意：尽量避免直接对String类型对象进行修改，因为String类是不能修改的，所有的修改都会创建新对象，效率非常低下

        long start = System.currentTimeMillis();
        String s = "";
        for (int i = 0; i < 100000; ++i) {
            s += i;
        }
        long end = System.currentTimeMillis();
        System.out.println(end - start);
        start = System.currentTimeMillis();
        StringBuffer sbf = new StringBuffer("");
        for (int i = 0; i < 100000; ++i) {
            sbf.append(i);
        }
        end = System.currentTimeMillis();
        System.out.println(end - start);
        start = System.currentTimeMillis();
        StringBuilder sbd = new StringBuilder();
        for (int i = 0; i < 100000; ++i) {
            sbd.append(i);
        }
        end = System.currentTimeMillis();
        System.out.println(end - start);

        //可以看到在对String类进行修改时，效率是非常慢的
        //如果要修改建议尽量使用StringBuffer或者StringBuilder
    }

    //由于String的不可更改特性，为了方便字符串的修改，Java中又提供StringBuilder和StringBuffer类
    //这两个类大部分功能是相同的，这里学习StringBuffer常用的一些方法
    public static void test12() {
        //StringBuff append(String str)
        //在尾部追加，相当于String的+=，可以追加：boolean、char、char[]、double、float、int、long、Object、String、StringBuff的变量
        StringBuffer stringBuffer = new StringBuffer("Hello");
        stringBuffer.append(" World");
        stringBuffer.append(123);
        stringBuffer.append(true);
        System.out.println(stringBuffer);

        //char charAt(int index)
        //获取index位置的字符
        System.out.println(stringBuffer.charAt(1));

        //int length()
        //获取字符串的长度
        System.out.println(stringBuffer.length());

        //int capacity()
        //获取底层保存字符串空间总的大小
        //capacity 和 length 的关系：
        //length ≤ capacity
        //length 表示当前字符数，而 capacity 表示可以存储的最大字符数
        //当字符数超过 capacity 时，StringBuffer 会自动扩容以适应新的字符数
        //创建一个 StringBuffer 对象时，如果没有指定初始容量，默认容量为16个字符
        //当字符超过当前容量时，StringBuffer 会增加其容量，新的容量通常是旧容量的两倍加二,但是超过34之后,就一个一个进行扩容
        //也就是16->34->35->36->37........
        StringBuffer stringBuffer1 = new StringBuffer();
        int capacity = stringBuffer1.capacity();
        System.out.println(capacity); // 输出: 16 (默认初始容量)
        stringBuffer1.append("Hello World");
        capacity = stringBuffer1.capacity();
        System.out.println(capacity); // 输出: 16 (没有超过初始容量)
        stringBuffer1.append("This is a longer string that will exceed the initial capacity");
        capacity = stringBuffer1.capacity();
        System.out.println(capacity); // 输出: 65 (扩容后的容量)

        //void ensureCapacity(int mininmumCapacity)
        //扩容,确保 StringBuffer 的容量至少为 minimumCapacity
        StringBuffer stringBuffer2 = new StringBuffer();
        stringBuffer2.ensureCapacity(50);
        System.out.println(stringBuffer2.capacity());

        //void setCharAt(int index, char ch)
        //将index位置的字符设置为ch
        stringBuffer.setCharAt(1, 'x');
        System.out.println(stringBuffer);

        //int indexOf(String str)
        //返回str第一次出现的位置
        StringBuffer stringBuffer3 = new StringBuffer("Hello");
        System.out.println(stringBuffer3.indexOf("el"));

        //int indexOf(String str, int fromIndex)
        //从fromIndex位置开始查找str第一次出现的位置
        StringBuffer stringBuffer4 = new StringBuffer("Hello World World");
        System.out.println(stringBuffer4.indexOf("World", 7));

        //int lastIndexOf(String str)
        //返回最后一次出现str的位置,反向查找第一次位置
        System.out.println(stringBuffer4.lastIndexOf("World"));

        //int lastIndexOf(String str, int fromIndex)
        //从指定索引位置开始，反向查找子字符串最后一次出现的位置
        StringBuffer stringBuffer5 = new StringBuffer("Hello World World");
        System.out.println(stringBuffer5.lastIndexOf("World", 11));

        //StringBuff insert(int offset, String str)
        //在offset位置插入：八种基类类型 & String类型 & Object类型数据
        //插入Object类型数据时，StringBuffer会调用Object的toString方法将其转换为字符串，然后插入到指定位置
        StringBuffer stringBuffer6 = new StringBuffer("Hello World");
        stringBuffer6.insert(5, ", Beautiful");
        System.out.println(stringBuffer6);
        stringBuffer6.insert(0, 123);
        System.out.println(stringBuffer6);

        //StringBuffer deleteCharAt(int index)
        //删除index位置字符
        stringBuffer6.deleteCharAt(1);
        System.out.println(stringBuffer6);

        //StringBuffer delete(int start, int end)
        //删除[start, end)区间内的字符
        stringBuffer6.delete(0, 2);
        System.out.println(stringBuffer6);

        //StringBuffer replace(int start, int end, String str)
        // 将[start, end)位置的字符替换为str
        stringBuffer6.replace(6, 16, "");
        System.out.println(stringBuffer6);

        //String substring(int start)
        //从start开始一直到末尾的字符以String的方式返回
        System.out.println(stringBuffer6.substring(7));

        //String substring(int start，int end)
        //将[start, end)范围内的字符以String的方式返回
        System.out.println(stringBuffer6.substring(0, 5));

        //String Buffer reverse()
        //反转字符串
        System.out.println(stringBuffer6.reverse());

        //String toString()
        //将所有字符按照String的方式返回
        System.out.println(stringBuffer6.toString());

        //从上述例子可以看出：String和StringBuffer最大的区别在于String的内容无法修改
        //而StringBuffer的内容可以修改。频繁修改字符串的情况考虑使用StringBuffer
        //注意：String和StringBuffer类不能直接转换。如果要想互相转换，可以采用如下原则:
        //String变为StringBuffer: 利用StringBuffer的构造方法或append()方法
        //StringBuffer变为String: 调用toString()方法。

        //String、StringBuffer、StringBuilder的区别
        //String的内容不可修改，StringBuffer与StringBuilder的内容可以修改
        //StringBuffer与StringBuilder大部分功能是相似的
        //StringBuffer采用同步处理，属于线程安全操作；而StringBuilder未采用同步处理，属于线程不安全操作
    }

    public static void test13() {
        String s1 = "hello";
        String s2 = "hello";
        String s3 = new String("hello");
        String s4 = new String("hello");
        System.out.println(s1 == s2); // true
        System.out.println(s1 == s3); // false
        System.out.println(s3 == s4); // false
    }

    public static void test14() throws NoSuchFieldException, IllegalAccessException {
        //这里说说Java的字符常量池
        //这个常量池在堆当中,他的底层结构是HashTable,这个HashTable的kv分别是字符串对象的的hashcode以及字符串对象的引用地址
        //存储新字符串字面值(编译期完成)：
        //当一个新的字符串字面值出现时，JVM计算该字符串的哈希码
        //JVM在哈希表中查找该哈希码
        //如果哈希表中不存在该哈希码，则创建一个新的字符串对象，并将其哈希码作为键，对象引用作为值存储在哈希表中
        //查找现有字符串字面值：
        //当需要查找一个字符串时，JVM计算该字符串的哈希码
        //JVM在哈希表中查找该哈希码
        //如果找到该哈希码，则返回对应的字符串对象引用
        //使用 intern() 方法：
        //intern() 方法会在字符串常量池中查找是否存在相同内容的字符串
        //如果存在，返回该字符串对象的引用；如果不存在，将该字符串添加到常量池中，并返回其引用

        //当第一次遇见一个字符串的时候,不管是String s1 = "hello"的形式还是String s3 = new String("hello");的形式
        //都会执行下面的流程(编译时)
        //当编译器遇到字符串字面值（例如 "hello"）时，会执行以下步骤：
        //编译阶段：
        //编译器会将所有字符串字面值收集到一个常量池中。这些字面值会被存储在 .class 文件中
        //例如，"hello" 字面值会被记录在类文件的常量池部分。
        //类加载阶段：
        //当 JVM 加载类时，会将类文件的常量池部分加载到运行时常量池（Runtime Constant Pool）中
        //这包括所有的字符串字面值
        //字符串对象的创建：
        //当类被首次加载和初始化时，JVM 会在字符串常量池中创建相应的 String 对象。这些对象是由 JVM 隐式创建的，而不是通过显式的 new String()。
        //字符串对象放入常量池：
        //这些 String 对象会被放入字符串常量池中，以便在运行时可以重用相同的字符串字面值

        //随后再执行下面的流程(运行时)
        //如果是String s1 = "hello",直接去字符串池中查找"hello",根据hello这个对象的哈希值来进行查找,将对应的引用返回给s1
        //所以s1的值就是字符串池中的"hello"对象的引用
        //底层的哈希表保存了hello这个String的引用,这个哈希表在这里只是提供了O(1)的时间查找
        //如果是String s3 = new String("hello");,直接去字符串池中查找"hello",根据hello这个对象的哈希值来进行查找,将对应的引用返回作为构造函数的参数
        //所以s3这个String对象不是字符串池中的那个对象,但是因为使用了字符串池中的那个对象的引用作为参数
        //所以s3中的value数组和字符串池中的那个对象的value数组是一样的,但是其实不影响
        //因为String中的value数组就是不允许修改的


        //如果是        char[] ch = new char[]{'a', 'b', 'c'};
        //             String s3 = new String(ch);
        //这种情况就不太一样了,下面都是运行时的过程,不会有编译时的在字符串常量池中创建相应的 String 对象
        //创建一个字符数组 ch，内容为 {'a', 'b', 'c'}。
        //然后，new String(ch) 创建一个新的 String 对象 s3，其内容为 "abc"，存储在堆中
        //此时，JVM 不会在常量池中创建 "abc" 字符串对象，因为 "abc" 是通过字符数组动态构建的，而不是字面值
        //因此，s3 是一个独立的对象，其 value 数组是 ch 的副本

        // 创建字符串字面值 "hello"
        String s1 = "hello";

        // 通过字面值创建字符串对象
        String s2 = new String("hello");

        // 通过字符数组创建字符串对象
        char[] ch = new char[]{'a', 'b', 'c'};
        String s3 = new String(ch); // s3 不在常量池中,常量池中没有"abc"

        // 调用 intern 方法
        String s4 = s3.intern(); // 将 "abc" 放入常量池中

        // 创建一个字面值字符串 "abc"
        String s5 = "abc"; // 这行代码会在常量池中创建 "abc" 字符串,但是我们之前已经有了

        // 使用反射获取内部的 value 数组引用
        Field valueField = String.class.getDeclaredField("value");
        valueField.setAccessible(true);

        byte[] value1 = (byte[]) valueField.get(s1);
        byte[] value2 = (byte[]) valueField.get(s2);
        byte[] value3 = (byte[]) valueField.get(s3);
        byte[] value4 = (byte[]) valueField.get(s4);
        byte[] value5 = (byte[]) valueField.get(s5);

        // 打印内部字符数组引用
        System.out.println("s1 value array hash: " + System.identityHashCode(value1));
        System.out.println("s2 value array hash: " + System.identityHashCode(value2));
        System.out.println("s3 value array hash: " + System.identityHashCode(value3));
        System.out.println("s4 value array hash: " + System.identityHashCode(value4));
        System.out.println("s5 value array hash: " + System.identityHashCode(value5));

        System.out.println(s1 == s2); // false
        System.out.println(s2 == s3); // false
        System.out.println(s3 == s4); // true
        System.out.println(s4 == s5); // true
        System.out.println(value1 == value2); // true
        System.out.println(value3 == value4); // ture
        System.out.println(value4 == value5); // true
    }


    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, UnsupportedEncodingException {
//        test1();
//        test2();
//        test3();
//        test4();
//        test5();
//        test6();
//        test7();
//        test8();
//        test9();
//        test10();
//        test11();
//        test12();
//        test13();
        test14();
    }


}
